﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.PointOfService;
using System.Reflection;
using CdcSoftware.Pivotal.Engine.Client.Services.Interfaces;
using System.Data;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace Pcm.Retail.Client.FormTasks
{
    [Serializable]
    public abstract class PosHardware : MarshalByRefObject
    {
        public static AppDomain domain = null;
        /// <summary>
        /// Abstract Class to perform Printing and Cash Drawer opening operation. For the CashDrawer only parameter to be passed is the DeviceLogicalName.
        /// </summary>
        /// <param name="DeviceLogicalName"></param>
        /// <param name="IsTest"></param>
        /// <param name="TenderDataset"></param>
        /// <param name="exchange"></param>
        /// <param name="rePrint"></param>
        /// <param name="gift"></param>
        /// <param name="receiptData"></param>
        /// <param name="lobTC"></param>
        /// <param name="staffPurchaseQuota"></param>
        /// <param name="quantity"></param>
        /// <param name="gstPercent"></param>
        public abstract void PerformActivity(string DeviceLogicalName, bool IsTest, DataSet TenderDataset, bool exchange,
            bool rePrint, bool gift, DataTable receiptData, DataTable lobTC, DataTable staffPurchaseQuota, DataTable quantity, object gstPercent);

        /// <summary>
        /// Create new AppDomain in which the POSHardwares will be operating as this domain will be having the CAS Enabled.
        /// </summary>
        /// <returns></returns>
        private static AppDomain CreateNewAppDomain()
        {
            var curr = AppDomain.CurrentDomain.SetupInformation;
            var info = new AppDomainSetup()
            {
                ApplicationBase = curr.ApplicationBase,
                LoaderOptimization = curr.LoaderOptimization,
                ConfigurationFile = curr.ConfigurationFile,
            };
            info.SetCompatibilitySwitches(new[] { "NetFx40_LegacySecurityPolicy" });
            return AppDomain.CreateDomain("POS Hardware AppDomain", null, info);
        }

        /// <summary>
        /// This is responsible for the Printing in the new AppDomain.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="DeviceLogicalName"></param>
        /// <param name="IsTest"></param>
        /// <param name="TenderDataset"></param>
        /// <param name="exchange"></param>
        /// <param name="rePrint"></param>
        /// <param name="gift"></param>
        /// <param name="receiptData"></param>
        /// <param name="lobTC"></param>
        /// <param name="staffPurchaseQuota"></param>
        /// <param name="quantity"></param>
        /// <param name="gstPercent"></param>
        public static void PerformActivity<T>(string DeviceLogicalName, bool IsTest, DataSet TenderDataset, bool exchange, bool rePrint, bool gift, 
            DataTable receiptData, DataTable lobTC, DataTable staffPurchaseQuota, DataTable quantity, object gstPercent) where T : PosHardware, new()
        {
            try
            {
                //If the Domain is already exisiting then there is no need to recreate again. We can use the same object.
                if(domain == null)
                    domain = CreateNewAppDomain();
                T hardware = (T)domain.CreateInstanceFromAndUnwrap(Assembly.GetAssembly(typeof(T)).Location, typeof(T).FullName);
                //Perform the Activity in the New Domain
                hardware.PerformActivity(DeviceLogicalName, IsTest, TenderDataset, exchange, rePrint, gift, receiptData, lobTC, staffPurchaseQuota, quantity, gstPercent);
                //Unload the Exception
                //AppDomain.Unload(domain);
            }
            catch (CannotUnloadAppDomainException ex)
            {
            }
        }
    }

    [Serializable]
    public class PivotalPrinter : PosHardware
    {
        public override void PerformActivity(string DeviceLogicalName, bool IsTest, DataSet TenderDataset, bool exchange,
            bool rePrint, bool gift, DataTable receiptData, DataTable lobTC, DataTable staffPurchaseQuota, DataTable quantity, object gstPercent)
        {
            //Anything inside this is in the new AppDomain.
            PosExplorer posExplorer = new PosExplorer();
            DeviceCollection devices = posExplorer.GetDevices(DeviceType.PosPrinter);
            DeviceInfo receiptPrinterDevice = posExplorer.GetDevice(DeviceType.PosPrinter, DeviceLogicalName);
            PosPrinter printer = (PosPrinter)posExplorer.CreateInstance(receiptPrinterDevice);
            //If the boolean is set means that it is a test print. Else it is regular print of the receipt.
            if (IsTest)
                TestPrint(printer);
            else
                PrintActual(printer, TenderDataset, exchange, rePrint, gift, receiptData, lobTC, staffPurchaseQuota, quantity, gstPercent);
        }

        /// <summary>
        /// Print the actual receipt.
        /// </summary>
        /// <param name="printer"></param>
        /// <param name="TenderDataset"></param>
        /// <param name="exchange"></param>
        /// <param name="rePrint"></param>
        /// <param name="gift"></param>
        /// <param name="receiptData"></param>
        /// <param name="lobTC"></param>
        /// <param name="staffPurchaseQuota"></param>
        /// <param name="quantity"></param>
        /// <param name="gstPercent"></param>
        private void PrintActual(PosPrinter printer, DataSet TenderDataset, bool exchange,
            bool rePrint, bool gift, DataTable receiptData, DataTable lobTC, DataTable staffPurchaseQuota, DataTable quantity, object gstPercent)
        {
            try
            {
                Globals.ConnectToPrinter(printer);
                if (printer != null && printer.Claimed && printer.DeviceEnabled)
                {
                    printer.TransactionPrint(PrinterStation.Receipt, PrinterTransactionControl.Transaction);
                    RETender tender = new RETender();
                    //Print Normal Receipt.
                    tender.PrintReceipt(printer, exchange, rePrint, false, TenderDataset, receiptData, lobTC, staffPurchaseQuota, quantity, gstPercent);

                    //Print Gift receipt.
                    if (gift)
                        tender.PrintReceipt(printer, exchange, rePrint, true, TenderDataset, receiptData, lobTC, staffPurchaseQuota, quantity, gstPercent);
                    printer.TransactionPrint(PrinterStation.Receipt, PrinterTransactionControl.Normal);
                }
            }
            catch (Exception ex)
            {
                Globals.HandleException(ex);
            }
            finally
            {
                if (printer != null && printer.Claimed && printer.DeviceEnabled)
                    Globals.DisconnectFromPrinter(printer);
            }
        }

        /// <summary>
        /// Print a test Sample to check if the Printer is properly connected or not.
        /// </summary>
        /// <param name="printer"></param>
        private void TestPrint(PosPrinter printer)
        {
            try
            {
                Globals.ConnectToPrinter(printer);
                if (printer != null && printer.Claimed && printer.DeviceEnabled)
                {
                    printer.TransactionPrint(PrinterStation.Receipt, PrinterTransactionControl.Transaction);

                    //Print Sample
                    Globals.PrintTextLine(printer, Environment.NewLine);
                    Globals.PrintTextLine(printer, new string('*', printer.RecLineChars));
                    Globals.PrintTextLine(printer, new string('*', printer.RecLineChars));
                    Globals.PrintTextLine(printer, System.Text.ASCIIEncoding.ASCII.GetString(new byte[] { 27, (byte)'|', (byte)'c', (byte)'A' }) +
                            System.Text.ASCIIEncoding.ASCII.GetString(new byte[] { 27, (byte)'|', (byte)'2', (byte)'h', (byte)'C' }) + "This is a sample print.");
                    Globals.PrintTextLine(printer, new string('*', printer.RecLineChars));
                    Globals.PrintTextLine(printer, new string('*', printer.RecLineChars));
                    Globals.PrintTextLine(printer, Environment.NewLine);
                    Globals.PrintTextLine(printer, Environment.NewLine);
                    Globals.PrintTextLine(printer, Environment.NewLine);
                    Globals.PrintTextLine(printer, Environment.NewLine);
                    printer.CutPaper(95);

                    printer.TransactionPrint(PrinterStation.Receipt, PrinterTransactionControl.Normal);
                }
            }
            catch (Exception ex)
            {
                Globals.HandleException(ex);
            }
            finally
            {
                if (printer != null && printer.Claimed && printer.DeviceEnabled)
                    Globals.DisconnectFromPrinter(printer);
            }
        }
    }

    [Serializable]
    public class PivotalCashDrawer : PosHardware
    {
        public override void PerformActivity(string DeviceLogicalName, bool IsTest, DataSet TenderDataset, bool exchange,
            bool rePrint, bool gift, DataTable receiptData, DataTable lobTC, DataTable staffPurchaseQuota, DataTable quantity, object gstPercent)
        {
            //Anything inside this is in the new AppDomain.            
            OpenCashDrawer(DeviceLogicalName);
        }

        /// <summary>
        /// Open the Cash Drawer.
        /// </summary>
        /// <param name="DeviceLogicalName"></param>
        private void OpenCashDrawer(string DeviceLogicalName)
        {
            CashDrawer cashDrawer = null;
            bool CashDrawerError = false;
            try
            {
                //Get the device.
                PosExplorer posExplorer = new PosExplorer();
                DeviceCollection devices = posExplorer.GetDevices(DeviceType.CashDrawer);
                DeviceInfo cashDrawerDevice = posExplorer.GetDevice(DeviceType.CashDrawer, DeviceLogicalName);
                cashDrawer = (CashDrawer)posExplorer.CreateInstance(cashDrawerDevice);

                if (cashDrawer != null)
                {
                    //Connect to the device
                    cashDrawer.Open();
                    cashDrawer.Claim(9000);
                    cashDrawer.PowerNotify = PowerNotification.Enabled;
                    cashDrawer.DeviceEnabled = true;

                    //Open the Drawer.
                    cashDrawer.OpenDrawer();
                }
            }
            catch (PosControlException)
            {
                MessageBox.Show("Cash Drawer is not configured properly. Please check the cash drawer.", "Pivotal Client", MessageBoxButtons.OK, MessageBoxIcon.Error);
                CashDrawerError = true;
            }
            catch (PosException)
            {
                MessageBox.Show("Cash Drawer is not configured properly. Please check the cash drawer.", "Pivotal Client", MessageBoxButtons.OK, MessageBoxIcon.Error);
                CashDrawerError = true;
            }
            catch (Exception ex)
            {
                CashDrawerError = true;
                throw ex;
            }
            finally
            {
                if (!CashDrawerError)
                {
                    cashDrawer.Release();
                    cashDrawer.Close();
                }
            }
        }
    }
}
